package cn.tk.utils;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;

/**
 * Created by Trace on 2019-07-16.<br>
 * Desc: HTTP Client工具类
 */
@Slf4j
public final class HttpClient {
    private static final List<NameValuePair> EMPTY_LIST = new ArrayList<>();
    private final static CloseableHttpClient HTTP_CLIENT, HTTPS_CLIENT;

    static {
        int timeout = 15_000;
        HttpClientBuilder builder = HttpClients.custom()
                .setDefaultRequestConfig(RequestConfig.custom()
                        .setConnectTimeout(timeout).setConnectionRequestTimeout(timeout).build());
        HTTP_CLIENT = builder.build();
        HTTPS_CLIENT = builder.setSSLSocketFactory(createSslConnSocketFactory()).build();
    }

    /**
     * 私有化构造
     */
    private HttpClient() {}


    /**
     * 发送HTTP GET请求，获取响应内容<br/>
     * @param url  请求地址
     * @return 响应内容, 失败则返回null
     */
    public static String httpGet(String url) {
        return httpGet(url, EMPTY_LIST);
    }


    /**
     * 发送HTTP GET请求，获取响应内容<br/>
     * @param url 请求地址
     * @param params 请求参数
     * @return 响应内容, 失败则返回null
     */
    public static String httpGet(String url, List<NameValuePair> params) {
        HttpGet httpGet = new HttpGet(joiner(url, params));
        return requestAndResponse(HTTP_CLIENT, httpGet);
    }


    /**
     * 发送HTTPS GET请求，获取响应内容<br/>
     * @param url 请求地址
     * @return 响应内容, 失败则返回null
     */
    public static String httpsGet(String url) {
        return httpsGet(url, EMPTY_LIST);
    }


    /**
     * 发送HTTPS Get请求，获取响应内容<br/>
     * @param url  请求地址
     * @return 响应内容, 失败则返回null
     */
    public static String httpsGet(String  url, List<NameValuePair> params) {
        HttpGet httpGet = new HttpGet(joiner(url, params));
        return requestAndResponse(HTTPS_CLIENT, httpGet);
    }


    /**
     * 发送HTTP POST请求，获取响应内容<br/>
     * @param url 请求地址
     * @return 响应内容, 失败则返回null
     */
    public static String httpPost(String url) {
        return httpPost(url, EMPTY_LIST);
    }


    /**
     * 发送HTTP POST请求，获取响应内容<br/>
     * @param url 请求地址
     * @param params 参数
     * @return 响应内容, 失败则返回null
     */
    public static String httpPost(String url, List<NameValuePair> params) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
        return requestAndResponse(HTTP_CLIENT, httpPost);
    }


    /**
     * 通过请求体的方式发送post请求，获取响应.<br/>
     * @param url 地址
     * @param body 请求体
     * @return 响应，失败返回null
     */
    public static String httpPostBody(String url, Object body) {
        if (body == null) {return httpPost(url);}
        String bodyStr = JSON.toJSONString(body);
        ByteArrayEntity entity = new ByteArrayEntity(bodyStr.getBytes(StandardCharsets.UTF_8));
        entity.setContentType("application/json");
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(entity);
        return requestAndResponse(HTTP_CLIENT, httpPost);
    }


    /**
     * HTTPS POST请求，获取响应结果<br/>
     * @param url 请求地址
     * @return 响应结果, 失败则返回null
     */
    public static String httpsPost(String url) {
        return httpsPost(url, EMPTY_LIST);
    }


    /**
     * HTTPS POST请求，获取响应结果<br/>
     * @param url 请求地址
     * @param params 请求参数
     * @return 响应消息, 失败则返回null
     */
    public static String httpsPost(String url, List<NameValuePair> params) {
        HttpPost httpsPost = new HttpPost(url);
        httpsPost.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
        return requestAndResponse(HTTPS_CLIENT, httpsPost);
    }


    /**
     * 创建SSL安全连接<br/>
     * @return SSLConnectionSocketFactory
     */
    private static SSLConnectionSocketFactory createSslConnSocketFactory() {
        SSLContext sslContext;
        try {
           sslContext = new SSLContextBuilder().loadTrustMaterial(null, (x, s) -> true).build();
            return new SSLConnectionSocketFactory(sslContext, (s, sslSession) -> true);
        } catch (Exception e) {
            log.error("创建SSL安全连接失败: {}", e.getMessage());
            return null;
        }
    }


    /**
     * 拼接url.<br/>
     * @param url 地址
     * @param params 请求参数对
     * @return 最终url
     */
    private static String joiner(String url, List<NameValuePair> params) {
        if (params == null || params.size() == 0) {
            return url;
        }
        StringJoiner joiner = new StringJoiner("&", "?", "");
        params.parallelStream().forEach(item -> joiner.add(item.toString()));
        return url + joiner.toString();
    }


    /**
     * 请求并获取相应内容, 响应失败则返回null.<br/>
     * @param client http客户端
     * @param request 请求实体
     * @return 响应内容
     */
    private static String requestAndResponse(CloseableHttpClient client, HttpUriRequest request) {
        int ok = 200;
        try (CloseableHttpResponse response = client.execute(request)) {
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode != ok) {
                log.error(response.toString());
                return null;
            }
            HttpEntity model = response.getEntity();
            return model == null ? null : EntityUtils.toString(model, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}
